package application;

import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.glBlendFunc;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glOrtho;
import static org.lwjgl.opengl.GL11.glViewport;

import java.io.Serializable;
import java.util.List;

import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.PixelFormat;

/**
 * Class which creates window and initialize OpenGL and also other
 * necessary functions.
 * 
 * It uses LWJGL library to achieve this goals.
 * 
 * @author Tomas Zima, Drahomir Karchnak
 */
public class Window implements Serializable
{
	/**
	 * Constructor.
	 * Create window with defined parameters.<
	 * 
	 * @param width
	 * 	Width of the window (or resolution) in pixels.
	 * 
	 * @param height
	 * 	Height of the window (or resolution) in pixels.
	 * 
	 * @param title
	 * 	Title of the window.
	 * 
	 * @param fullscreen
	 *  Set true to switch to fullscreen mode or false for window mode.
	 */
	public Window(int width, int height, String title, boolean fullscreen)
	{
		// Set attributes
		this.setSize(width, height);
		this.setTitle(title);
		
		this.fullscreen = fullscreen;
		
		// Create window using these attributes
		reset();
		resetComponents();
	}
	
	/**
	 * Constructor.
	 * Create window with defined parameters.
	 * 
	 * @param width
	 * 	Width of the window in pixels.
	 * 
	 * @param height
	 * 	Height of the window in pixels.
	 * 
	 * @param title
	 * 	Title of the window (can be changed later).
	 */
	public Window(int width, int height, String title, List<String> strings)
	{		
		// Set attributes
		this.setSize(width, height);
		this.setTitle(title);
		
		// Create window using these attributes
		reset();
		resetComponents();		
	}

	/**
	 * Constructor.
	 * Create window with defined parameters.
	 * 
	 * @param width
	 * 	Width of the window in pixels.
	 * 
	 * @param height
	 * 	Height of the window in pixels.
	 */
	public Window(int width, int height)
	{
		// Set attributes
		this.setSize(width, height);
		
		// Create window using these attributes
		this.reset();
		resetComponents();		
	}
	
	/**
	 * Sets size of the window. Cannot be redefined later.
	 * 
	 * @param width
	 * 	Width of the window in pixels.
	 * 
	 * @param height
	 * 	Height of the window in pixels.
	 */
	private void setSize(int width, int height)
	{
		this.width	= width;
		this.height	= height;
	}
	

	/**
	 * Sets title of the window. It's possible to change
	 * it whenever you like.
	 * 
	 * @param title
	 */
	public void setTitle(String title)
	{
		this.title = title;
	}
	
	/**
	 * Returns width of the window in pixels.
	 * 
	 * @return
	 * 	Width of the window in pixels.
	 */
	public int getWidth()
	{
		return width;
	}
	
	/**
	 * Returns height of the window in pixels.
	 * 
	 * @return
	 * 	Height of the window in pixels.
	 */
	public int getHeight()
	{
		return height;
	}
	
	/**
	 * Returns title of the window.
	 * 
	 * @return
	 * 	Title of the window.
	 */
	public String getTitle()
	{
		return title;
	}
	
	/**
	 * Enables fullscreen mode.
	 */
	public void enableFullscreen()
	{
		fullscreen = true;
	}

	/**
	 * Disables fullscreen mode.
	 */
	public void disableFullscreen()
	{
		fullscreen = false;
	}
	
	/**
	 * Returns true if fullscreen mode is enabled and false if it's not.
	 * 
	 * @return
	 * 	True: enabled, false: disabled.
	 */
	public boolean isFullscreenEnabled()
	{
		return fullscreen;
	}
	
	/**
	 * If you set color before drawing a texture, the result image will be
	 * composed from texture and this color as well.
	 */
	public static void enableTexColorMixing()
	{
		colorTexMixing = true;
		
		GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
	}
	
	/**
	 * Default acting when texturing is enabled. If you set color before
	 * drawing a texture, the color will be ignored and only texture will
	 * be drawn without any differences.
	 */
	public static void disableTexColorMixing()
	{
		colorTexMixing = false;
		
		GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_DECAL);
	}
	
	/**
	 * See description of enableTexColorMixing() and disableTexColorMixing() methods
	 * to understand what this mean.
	 *
	 * @return
	 * 	True: enabled, false: disabled
	 */
	public static boolean isTexColorMixingEnabled()
	{
		return colorTexMixing;
	}
	
	/**
	 * Enable texturing.
	 */
	public static void enableTexturing()
	{
		texturingEnabled = true;
		
		glEnable(GL_TEXTURE_2D);
	}
	
	/**
	 * Disable texturing.
	 */
	public static void disableTexturing()
	{
		texturingEnabled = false;
		
		glDisable(GL_TEXTURE_2D);
	}
	
	/**
	 * Checks if texturing is enabled.
	 * 
	 * @return
	 */
	public static boolean isTexturingEnabled()
	{
		return texturingEnabled;
	}
	
	/**
	 * Reinitializes window.
	 */
	public void reset()
	{
		// If there currently exists display, destroy it
		if (Display.isCreated())
		{
			Display.destroy();
		}
		
		// If title is empty, set it to current window size
		if (title == "")
		{
			title = String.valueOf(width) + "x" + String.valueOf(height);
		}
		
		// Check errors
		try
		{
			// Set resolution
			if (!fullscreen)
			{
				Display.setDisplayMode(new DisplayMode(width, height));
			}
			else
			{
				// Get list of all available display modes
				DisplayMode modes[] = Display.getAvailableDisplayModes();
				
				int i = 0;
				boolean success = false;
				
				// Find first display mode with required width and height.
				for (; i < modes.length; i++)
				{
					// Rule #1: fullscreen available!
					if (!modes[i].isFullscreenCapable())
					{
						continue;
					}
					
					// Rule #2: width and depth
					if (modes[i].getWidth() == width && modes[i].getHeight() == height && modes[i].getBitsPerPixel() >= 16)
					{
						success = true;
						break;
					}
				}

				// Print extremely sad message!
				if (!success)
				{
					System.out.println("Desired display mode is not available.");
					System.exit(1);
				}
				else
				{
					Display.setDisplayMode(modes[i]);
					Display.setFullscreen(true);
				}
			}
			
			// Set window title
			Display.setTitle(title);

			// Set graphic context attributes
			PixelFormat pixelFormat = new PixelFormat();					// configures bit depth etc 
			ContextAttribs contextAtrributes = new ContextAttribs(1, 0);	// configures OpenGL version
			Display.create(pixelFormat, contextAtrributes);					// create window
	
			/*
			 * OpenGL initialization
			 */
			glViewport(0, 0, width, height);		// visible area
			glClearColor(0.0f, 0.0f, 0.0f, 0.0f);	// default background color
			glOrtho(0, width, height, 0, 1, -1);	// coordinates in pixels

			// Enable textures by default
			enableTexturing();
			
			// We don't want to mix color and texture together
			disableTexColorMixing();
			
			// Enable cull facing (render only front side of polygon)
			glEnable(GL11.GL_CULL_FACE);
			GL11.glFrontFace(GL11.GL_CW);

			// Enable blending (using alpha channel)
        	glEnable(GL_BLEND);
        	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		}
		catch (LWJGLException errHandler)
		{
			throw new RuntimeException("Error during LWJGL initialization.", errHandler);
		}
	}
	
	/**
	 * Initialize components such as keyboard, mouse etc.
	 * Do everything, what will be done definitely only once (no during every window resize etc).
	 */
	private void resetComponents()
	{
		try
		{
			Keyboard.create();
			Mouse.create();
		}
		catch (LWJGLException errHandler)
		{
			throw new RuntimeException("Error during LWJGL initialization.", errHandler);
		}
	}
	
	/**
	 * For "infinity" loops. Checks, if window still exists (and program should run).
	 * 
	 * @return
	 * 	True: yes, false: no.
	 */
	public boolean running()
	{
		return !Display.isCloseRequested();
	}
	
	/**
	 * Refresh the scene.
	 */
	public void update()
	{
		Display.update();
	}

	// Attributes section
	private int		width			= 0;
	private int		height			= 0;
	private String	title			= "";
	private boolean	fullscreen		= false;
	
	private static boolean colorTexMixing	= true;
	private static boolean texturingEnabled	= true;
}
